/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package control;

import entity.Player;
import java.awt.GridLayout;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import view.PlayerLattice;

/**
 *
 * @author laarguelless
 */
public class PlayersControl implements Runnable {

    private int rows;
    private int cols;
    private int rowsize;
    private int colsize;
    private Player[][] playersMatrix;
    private int generations;
    private int coopPercentage;
    private int reward;
    private int suckers;
    private int temptation;
    private int punishment;
    private boolean stop;
    private int count;

    public PlayersControl(int gen, int coop, int r, int s, int t, int p) {
        generations = gen;
        coopPercentage = coop;
        reward = r;
        suckers = s;
        temptation = t;
        punishment = p;
        stop = true;
        count = 1;
    }

    /**
     *
     * @param r rows
     * @param c cols
     *
     * recibe el número de filas y el número de columnas y con base a esto
     * inicializa las matrices de jugadores y de paneles...
     */
    public void setSize(int r, int c, int rs, int cs) {
        rows = r;
        cols = c;
        rowsize = rs;
        colsize = cs;

        playersMatrix = new Player[rows][cols];


    }

    /*
     * Devuelve el jugador que está en la posición (i, j) de la matriz de
     * jugadores
     *
     */
    public Player getPlayer(int i, int j) {
        return playersMatrix[i][j];
    }

    /**
     * Incializa el JPanel que contiene la grilla de jugadores. Incializa las
     * entidades y las vistas, y les asigna una de las 16 estrategias posibles.
     *
     * @return el JPanel
     */
    public JPanel intiGrid() {
        JPanel gridPanel = new JPanel();
        gridPanel.setLayout(new GridLayout(rows, cols));


        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                int profile = isCooperator();
                int[] strategy = getStrategy(profile);
                PlayerLattice lattice = new PlayerLattice(rowsize, colsize, profile, getStrategyLabel(strategy));
                Player player = new Player(1, strategy, i, j, generations, lattice);
                playersMatrix[i][j] = player;
                gridPanel.add(lattice);
            }
        }

        setNeighbors();
        return gridPanel;
    }

    /**
     *
     * @param profile 0 si es colaborador 1 si es traidor
     * @return un int[] que representa la estrategia
     */
    private int[] getStrategy(int profile) {

        Random random = new Random();
        int index = random.nextInt(4);
        return intToBytes(index, profile);
    }

    /**
     * Mapea el entero de la estrategia, generado mediante un random a un
     * arreglo de enteros
     *
     * @param number
     * @return int[] que representa la estrategia
     */
    private int[] intToBytes(int number, int profile) {
        int[][] coopStrategy = {{0, 0, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 1}, {0, 1, 1, 0}};
        int[][] defStrategy = {{1, 1, 1, 1}, {0, 1, 0, 0}, {0, 1, 0, 1}, {0, 1, 1, 0}};

        if (profile == 0) {
            return coopStrategy[number];
        } else {
            return defStrategy[number];
            //  return defStrategy[number];
        }

    }

    /**
     * Retorna la strategia como un String
     *
     * @param strategy
     * @return
     */
    private String getStrategyLabel(int[] strategy) {
        String[] labels = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"};
        int index = 0;
        int j = 0;
        for (int i = strategy.length - 1; i >= 0; i--) {
            index += Math.pow(2, i) * strategy[j];
            j++;
        }
        return labels[index];

    }

    /**
     * Define si un jugador dado inicia el juego como un traidor o un
     * cooperador, dependiendo del porcentaje ingresado en la configuración
     * inicial ....
     *
     * @return
     */
    private int isCooperator() {
        Random random = new Random();
        if (random.nextInt(100) < coopPercentage) {
            return 0;
        } else {
            return 3;
        }
    }

    private void setNeighbors() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                playersMatrix[i][j].setNeighbor(playersMatrix[getRow(i)][getCol(j - 1)]);
                playersMatrix[i][j].setNeighbor(playersMatrix[getRow(i - 1)][getCol(j)]);
                playersMatrix[i][j].setNeighbor(playersMatrix[getRow(i)][getCol(j + 1)]);
                playersMatrix[i][j].setNeighbor(playersMatrix[getRow(i + 1)][getCol(j)]);
            }
        }
    }

    private int getRow(int i) {
        if (i < 0) {
            return rows - 1;
        } else if (i >= rows) {
            return 0;
        } else {
            return i;
        }
    }

    private int getCol(int j) {
        if (j < 0) {
            return cols - 1;
        } else if (j >= cols) {
            return 0;
        } else {
            return j;
        }
    }

    @Override
    public void run() {

        this.stop = false;
        while (!stop) {
            try {
                simulate();
            } catch (InterruptedException ex) {
                Logger.getLogger(PlayersControl.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    private void calculatePayOff() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                /**
                 * Para cad jugador calcula el payOff con cada uno de sus
                 * vecinos *
                 */
                Player player = playersMatrix[i][j];
                List<Player> neighbors = player.getNeigbors();
                for (Player ne : neighbors) {
                    if (player.getLastBehaviour() == 0 && ne.getLastBehaviour() == 0) {
                        player.setPayOff(player.getPayOff() + reward);
                    } else if (player.getLastBehaviour() == 0 && ne.getLastBehaviour() == 1) {
                        player.setPayOff(player.getPayOff() + suckers);
                    } else if (player.getLastBehaviour() == 1 && ne.getLastBehaviour() == 0) {
                        player.setPayOff(player.getPayOff() + temptation);
                    } else if (player.getLastBehaviour() == 1 && ne.getLastBehaviour() == 1) {
                        player.setPayOff(player.getPayOff() + punishment);
                    }
                }
            }
        }
    }

    /**
     * Calcula el siguiente comportamiento a partir del comportamiento anterior
     * del jugador y de sus vecinos. Lo calcula para cada jugador.
     */
    private void calculateNextBehaviour() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                playersMatrix[i][j].calculateNextBehaviour();
            }
        }
    }

    /**
     * Realiza el próximo movimiento.
     */
    private void doNextMovement() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                playersMatrix[i][j].doNextMovement();
            }
        }
    }

    /**
     * Calcula la siguiente estrategia
     */
    public void calculateNextStrategy() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < rows; j++) {
                playersMatrix[i][j].calculateNextStrategy();
            }
        }
    }

    /**
     * Hace efectiva la siguiente estratégia *
     */
    public void setStrategy() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < rows; j++) {
                playersMatrix[i][j].setStrategy();
                String label = getStrategyLabel(playersMatrix[i][j].getStrategy());
                playersMatrix[i][j].setLabel(label);
            }
        }

    }

    /**
     * Simula un paso completo de todo el tablero ...
     *
     * @param count
     */
    private void simulate() throws InterruptedException {

        if (count > generations) {
            calculateNextStrategy();
            setStrategy();
            count = 1;
            Thread.sleep(100);
        } else {
            /**
             * Primero se calcula el payOff general de cada uno de los jugadores
             * *
             */
            calculatePayOff();
            /**
             * Luego se Calcula el próximo movimiento *
             */
            calculateNextBehaviour();
            /**
             * Luego se hace un nuevo movimiento *
             */
            doNextMovement();
            Thread.sleep(100);
            count++;
        }

    }

    /**
     * Detiene la ejecución al establecer la variable boolean stop en true
     */
    public void stop() {
        stop = true;
    }
}
